package com.pj.lib.auraClient;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.content.Context;
import android.net.ConnectivityManager;
import android.util.Log;


import com.pj.lib.aura.connection.NetworkMap;
import com.pj.lib.aura.connection.RoomModel;
import com.pj.lib.aura.protocol.AuraProtocol;
import com.pj.lib.aura.protocol.AuraProtocolMapKeys;
import com.pj.lib.auraClient.AuraService.ConnectionStatus;
import com.pj.lib.auraClient.exceptions.UserNotLoggedException;

public class AuraServiceConnectionThread extends Thread {
	private static final String TAG = "ServerConnectionThread";
	private static final long CONNECTION_SLEEP_TIME_AFTER_CONNECTION_ERROR = 10000;
	
	private String mServerAddress;
	private int mServerPort;
	private AuraService mService; 
	private Runnable mOnConnected;
	
	ConnectionStatus mStatus = ConnectionStatus.DISCONNECTED;
	ConnectivityManager mConnectivityManager;
	
	private int userID = -1;
	
	public AuraServiceConnectionThread(AuraService service, String serverAddress, int serverPort) {
		this.mServerAddress = serverAddress;
		this.mServerPort = serverPort;
		this.mService = service;
		
		mConnectivityManager =  (ConnectivityManager)service.getSystemService(Context.CONNECTIVITY_SERVICE);
	}
	@Override
	public void run() {
		while(true) {
			Log.i(TAG, "working...");
			if(mStatus.equals(ConnectionStatus.DISCONNECTED)) {
				Log.i(TAG, "disconnected");
				setConnectionStatus(ConnectionStatus.CONNECTING);
				Log.i(TAG, "connecting");
				try {
						if(checkConnection()) {
							Log.i(TAG, "server reachable");
							AuraConnection.getInstance().connect(mServerAddress, mServerPort);
							if(mOnConnected != null) {
								Log.i(TAG, "do on connected");
								mOnConnected.run();
								mOnConnected = null;
							}
							Log.i(TAG, "connected success");
							setConnectionStatus(ConnectionStatus.CONNECTED);
							Log.i(TAG, "connection status changed to connected");
							/*Bundle data = new Bundle();
							data.putString(String.valueOf(AuraProtocolMapKeys.FUNCTION_TYPE_KEY), 
									AuraProtocol.GET_SERVER_PROPERTIES);
							handleSendGetServerProperties(data);*/
						} else {
							Log.i(TAG, "connection false");
							
							setConnectionStatus(ConnectionStatus.DISCONNECTED);
							Log.i(TAG, "going to sleep...");
							this.sleep(CONNECTION_SLEEP_TIME_AFTER_CONNECTION_ERROR);
							Log.i(TAG, "after sleep...");
						}
				} catch (Exception e) {
					Log.i(TAG, "catched exception: " + e.getMessage());
					
					//sleep if cannot connect and after this try to connect again
					try {
						setConnectionStatus(ConnectionStatus.DISCONNECTED);
						Log.i("a", "going to sleep...");
						this.sleep(CONNECTION_SLEEP_TIME_AFTER_CONNECTION_ERROR);
						Log.i("a", "after sleep...");
						
					} catch (InterruptedException e1) {
						if(Aura.DEBUG) Log.e(TAG, "error in connection sleeping: " + e1.getMessage());
					}
				}
		
			} else if(mStatus.equals(ConnectionStatus.CONNECTED)) {
				Log.i(TAG, "connected");
				try {
					NetworkMap incomeMap = (NetworkMap)AuraConnection.getInstance().getIn().readObject();
					if(Aura.DEBUG) 
						Log.i(TAG, "object returned");
					invokeMethod(incomeMap, AuraService.FUNCTION_PREFIX_GET);
					if(Aura.DEBUG) 
						Log.i(TAG, "method invoked");
				}  catch (Exception e) {
					if(Aura.DEBUG) 
						Log.i(TAG, "exception in connected: " + e.getMessage());
					setConnectionStatus(ConnectionStatus.DISCONNECTED);
					if(Aura.DEBUG) 
						Log.e(TAG, "connection error: " + e.getMessage());
					
				}
				
			}
			
		}
	}
	
	private Boolean checkConnection() {
		
		if(!mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isConnected()
				&& !mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected() ) {
			return false;
		} else {
			return true;
		}
		
	}
	
	private void setConnectionStatus(ConnectionStatus status) {
		mStatus = status;
	
		mService.fireConnectionStatus(mStatus);
	}
	
	
	protected void invokeMethod( NetworkMap data, String functionPrefix) {
		try {
			Method method = getClass().getMethod(functionPrefix + data.get( AuraProtocolMapKeys.FUNCTION_TYPE_KEY), NetworkMap.class);
			method.invoke(this, data);
		} catch (SecurityException e) {
			if(Aura.DEBUG) {
				Log.e(TAG, e.getMessage(), e);
			}
		} catch (NoSuchMethodException e) {
			if(Aura.DEBUG) {
				Log.e(TAG, e.getMessage(), e);
			}
		} catch (IllegalArgumentException e) {
			if(Aura.DEBUG) {
				Log.e(TAG, e.getMessage(), e);
			}
		} catch (IllegalAccessException e) {
			if(Aura.DEBUG) {
				Log.e(TAG, e.getMessage(), e);
			}
		} catch (InvocationTargetException e) {
			if(Aura.DEBUG) {
				Log.e(TAG, e.getMessage(), e);
			}
		}
		
	}
	
	public void handleSendLogin(String username, String password) throws IOException {
    	NetworkMap map = new NetworkMap(AuraProtocol.LOGIN);
    	map.put(AuraProtocolMapKeys.USER_USERNAME_KEY, username);
    	map.put(AuraProtocolMapKeys.USER_PASSWORD_KEY, password);
    	
    	write(map);
    }
    
    public void handleSendCreateAccount(String username, String password) throws IOException {
    	NetworkMap outcomeMap = new NetworkMap(AuraProtocol.CREATE_ACCOUNT);
    	outcomeMap.put(AuraProtocolMapKeys.USER_USERNAME_KEY, username);
    	outcomeMap.put(AuraProtocolMapKeys.USER_PASSWORD_KEY, password);
    	
    	write(outcomeMap);
    }
    
    public void handleSendLocalization(double[] loc, float acc, String[] roomCodes) throws UserNotLoggedException, IOException{
    	if(userID == -1) {
    		throw new UserNotLoggedException();
    	} else {
    		NetworkMap outcomeMap = new NetworkMap(AuraProtocol.LOCALIZATION, userID);
        	outcomeMap.put(AuraProtocolMapKeys.LOCALIZATION, loc);
        	outcomeMap.put(AuraProtocolMapKeys.LOCALIZATION_ACCURACY, acc);
        	outcomeMap.put(AuraProtocolMapKeys.ROOM_CODE, roomCodes);
        	
        	write(outcomeMap);
    	}
    }
    
    /*public void handleSendAddAuraTypes(AuraTypeModel[] auraTypes, String roomCode) throws IOException {
    	NetworkMap outcomeMap = new NetworkMap(AuraProtocol.ADD_AURA_TYPE);
    	outcomeMap.put(AuraProtocolMapKeys.AURA_TYPES_KEY, auraTypes);
    	outcomeMap.put(AuraProtocolMapKeys.ROOM_CODE, roomCode);
    	write();
    }*/
    
    /*public void handleSendUpdateAuraType(AuraTypeModel auraType, String code) throws IOException {
    	outcomeMap = new NetworkMap(AuraProtocol.UPDATE_AURA_TYPE);
    	outcomeMap.put(AuraProtocolMapKeys.AURA_TYPES_KEY, auraType);
    	outcomeMap.put(AuraProtocolMapKeys.AURA_TYPE_CODE_KEY, code);
    	
    	write();
    	
    }*/
    
    /*public void handleSendDeleteAuraType(String code, String replacementCode) throws IOException {
    	outcomeMap = new NetworkMap(AuraProtocol.DELETE_AURA_TYPE);
    	outcomeMap.put(AuraProtocolMapKeys.AURA_TYPE_CODE_KEY, code);
    	outcomeMap.put(AuraProtocolMapKeys.AURA_TYPE_CODE_REPLACEMENT_KEY, replacementCode);
    	
    	write();
    }*/
    
    public void handleSendGetAuraType(String code, String roomCode) throws IOException{
    	NetworkMap outcomeMap = new NetworkMap(AuraProtocol.GET_AURA_TYPE);
    	if(code != null && code != "") {
    		outcomeMap.put(AuraProtocolMapKeys.AURA_TYPE_CODE_KEY, code);
    	}
    	if(roomCode != null && roomCode != "") {
    		outcomeMap.put(AuraProtocolMapKeys.ROOM_CODE, roomCode);
    	}
    	write(outcomeMap);
    }
    
    public void handleSendSetAura(String auraTypeCode, String roomCode) throws UserNotLoggedException, IOException{
    	if(userID == -1) {
    		throw new UserNotLoggedException();
    	} else {
    		handleSendSetAura(auraTypeCode, roomCode, userID);
    	}
    }
    public void handleSendSetAura(String auraTypeCode, String roomCode, int userId) throws IOException {
    	NetworkMap outcomeMap = new NetworkMap(AuraProtocol.SET_AURA_FOR_USER, userId, roomCode);
    	outcomeMap.put(AuraProtocolMapKeys.AURA_TYPE_CODE_KEY, auraTypeCode);
    	
    	write(outcomeMap);
    }
    
    public void handleSendDisableAura(String roomCode) throws UserNotLoggedException, IOException{
    	if(userID == -1) {
    		throw new UserNotLoggedException();
    	} else {
    		handleSendDisableAura(roomCode, userID);
    	}
    }

	private void handleSendDisableAura(String roomCode, int userId) throws IOException{
		NetworkMap outcomeMap = new NetworkMap(AuraProtocol.DISABLE_AURA_FOR_USER, userId, roomCode);
    	write(outcomeMap);
		
	}
	
	public void handleSendLogout() throws UserNotLoggedException, IOException {
		if(userID == -1) {
			throw new UserNotLoggedException();
		} else {
			NetworkMap outcomeMap = new NetworkMap(AuraProtocol.LOGOUT, userID);
			write(outcomeMap);
		}
	}
	
	public void handleSendAddRoom(RoomModel roomModel, Integer adminId) throws IOException {
		NetworkMap outcomeMap = new NetworkMap(AuraProtocol.ADD_ROOM, userID);
		outcomeMap.put(AuraProtocolMapKeys.ROOM_MODEL, roomModel);
		if(adminId != null) {
			outcomeMap.put(AuraProtocolMapKeys.USER_ID_KEY, adminId);
		}
		write(outcomeMap);
	}
	
	public void handleSendAddUserToRoom(String roomCode, String userType, String roomPassword) throws UserNotLoggedException, IOException {
		if(userID == -1) {
			throw new UserNotLoggedException();
		} else {
			NetworkMap outcomeMap = new NetworkMap(AuraProtocol.ADD_USER_TO_ROOM, userID);
			outcomeMap.put(AuraProtocolMapKeys.ROOM_CODE, roomCode);
			outcomeMap.put(AuraProtocolMapKeys.USER_ID_KEY, userID);
			outcomeMap.put(AuraProtocolMapKeys.USER_TYPE, userType);
			if(roomPassword != null && !roomPassword.equals("")) {
				outcomeMap.put(AuraProtocolMapKeys.ROOM_PASSWORD, roomPassword);
			}
			
			write(outcomeMap);
		}
	}
	
	public void handleSendChangeUserActive(String roomCode, Boolean userActive) throws UserNotLoggedException, IOException {
		if(userID == -1) {
			throw new UserNotLoggedException();
		} else {
			NetworkMap outcomeMap = new NetworkMap(AuraProtocol.CHANGE_USER_ACTIVE, userID);
			outcomeMap.put(AuraProtocolMapKeys.USER_ID_KEY, userID);
			outcomeMap.put(AuraProtocolMapKeys.ROOM_CODE, roomCode);
			outcomeMap.put(AuraProtocolMapKeys.USER_ACTIVE, userActive);
			
			write(outcomeMap);
		}
	}
	
    
	public void handleGetUserLocalizations(NetworkMap map) {
		//TODO dorobic
	}
	
	public void handleGetLogin(NetworkMap map) {
		
		Boolean result = (Boolean) map.getResult();
		if(result) {
			AuraPreferences.updateString(mService, AuraPreferences.KEY_LOGGED, "Y");
			userID = (Integer) map.get(AuraProtocolMapKeys.USER_ID_KEY);
			mService.fireLoginResult(result, 
					null, 
					userID);
			
		} else {
			mService.fireLoginResult(result, 
					map.getResultDescription(), 
					null);
		}
	}
	
	public void handleGetCreateAccount(NetworkMap map) {

		Boolean result = (Boolean) map.getResult();
		if(result) {
			mService.fireCreateAccountResult(result, 
					null, 
					(Integer) map.get(AuraProtocolMapKeys.USER_ID_KEY));
		} else {
			mService.fireCreateAccountResult(result, 
					map.getResultDescription(), 
					null);
		}
	}
	/*
	public void handleGetGetAuraType(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireAuraTypeQueried((AuraTypeModel[]) map.get(AuraProtocolMapKeys.AURA_TYPES_KEY),
					map.getRoomCode());
		} else {
			c.getController().fireResultFalseEvent(AuraProtocol.GET_AURA_TYPE, map.getResultDescription(), null);
		}
	}

	public void handleGetSetAura(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireUserAuraSetEvent((Integer) map.get(AuraProtocolMapKeys.CLIENT_OF_SERVICE_ID_KEY), 
					localIds.get(map.getRoomCode()));
		} else {
			c.getController().fireResultFalseEvent(AuraProtocol.SET_AURA_FOR_USER, map.getResultDescription(),null);
		}
	}
	
	public void handleGetDisableAura(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireUserAuraDisabledEvent((Integer) map.get(AuraProtocolMapKeys.CLIENT_OF_SERVICE_ID_KEY),
					localIds.get(map.getRoomCode()));
		} else {
			HashMap<String, LocalId> locMap =new HashMap<String, LocalId>();
			locMap.put(map.getRoomCode(), localIds.get(map.getRoomCode()));
			c.getController().fireResultFalseEvent(AuraProtocol.DISABLE_AURA_FOR_USER, map.getResultDescription(), locMap);
		}
	}
	
	public void handleGetAuraEvent(NetworkMap map) {
		System.out.println("aura event");
		c.getController().fireAuraEvent(
				(Integer) map.get(AuraProtocolMapKeys.ASU_ID),
				(Integer) map.get(AuraProtocolMapKeys.AC_ID), 
				(String) map.get(AuraProtocolMapKeys.AURA_ACTION), localIds.get(map.getRoomCode()));
	}
	
	public void handleGetAddAuraTypes(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireAuraTypesAdded();
		} else {
			c.getController().fireResultFalseEvent(AuraProtocol.ADD_AURA_TYPE, map.getResultDescription(), null);
		}
	}
	
	public void handleGetDeleteAuraType(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireAuraTypeDeleted();
		} else {
			c.getController().fireResultFalseEvent(AuraProtocol.DELETE_AURA_TYPE, map.getResultDescription(), null);
		}
	}
	
	public void handleGetUpdateAuraType(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireAuraTypeUpdated();
		} else {
			c.getController().fireResultFalseEvent(AuraProtocol.UPDATE_AURA_TYPE, map.getResultDescription(), null);
		}
	}

	public void handleGetAddRoom(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireRoomAdded();
		} else {

			c.getController().fireResultFalseEvent(AuraProtocol.ADD_ROOM, map.getResultDescription(), null);
		}
	}
	
	public void handleGetAddUserToRoom(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireUserAddedToRoom();
		} else {
			c.getController().fireResultFalseEvent(AuraProtocol.ADD_USER_TO_ROOM, map.getResultDescription(), localIds);
		}
	}
	
	public void handleGetChangeUserActive(NetworkMap map) {
		Boolean result = (Boolean) map.getResult();
		if(result) {
			c.getController().fireUserActiveChanged();
		} else {
			c.getController().fireResultFalseEvent(AuraProtocol.CHANGE_USER_ACTIVE, map.getResultDescription(), null);
		}
	}*/
	
	private void write(NetworkMap map) {
		try {
			AuraConnection.getInstance().write(map);
		} catch (Exception e) {
			setConnectionStatus(ConnectionStatus.DISCONNECTED);
			if(Aura.DEBUG) 
				Log.e(TAG, "connection error: " + e.getMessage());
		}
	}
	public void doOnConnected(Runnable onConnected) {
		mOnConnected = onConnected;
	}
}
